/**
 * (created at 2011-1-27)
 */
package qiushuo.mysqlparser.recognizer.parser;

import static qiushuo.mysqlparser.recognizer.SQLTokenType.IDENTIFIER;
import static qiushuo.mysqlparser.recognizer.SQLTokenType.KW_DEFAULT;
import static qiushuo.mysqlparser.recognizer.SQLTokenType.KW_KEY;
import static qiushuo.mysqlparser.recognizer.SQLTokenType.KW_UPDATE;
import static qiushuo.mysqlparser.recognizer.SQLTokenType.PUNC_LEFT_PAREN;

import java.sql.SQLSyntaxErrorException;
import java.util.LinkedList;
import java.util.List;

import qiushuo.mysqlparser.ast.expression.Expression;
import qiushuo.mysqlparser.ast.expression.primary.DefaultValue;
import qiushuo.mysqlparser.recognizer.SQLToken;
import qiushuo.mysqlparser.recognizer.TokenReader;

/**
 * @author <a href="mailto:QiuShuo1985@gmail.com">QIU Shuo</a>
 */
public abstract class DMLInsertReplaceParser extends DMLParser {
    public DMLInsertReplaceParser(TokenReader input) {
        super(input);
    }

    /**
     * start from element, at least one element, consume last ')'
     */
    protected void columnList(List<String> columnNameList) throws SQLSyntaxErrorException {
        while (true) {
            SQLToken token = input.next();
            if (token == null) err("unexpected EOF in columnList");
            columnNameList.add(token.getText());
            switch (token.getType()) {
            case PUNC_COMMA:
                continue;
            case PUNC_RIGHT_PAREN:
                return;
            default:
                err("unexpected token in columnList: " + token);
            }
        }
    }

    /**
     * first <code>VALUE|VALUES</code> has been consumed<br/>
     * consume followed <code>ON DUPLICATE KEY UPDATE</code> if there is one and
     * onDup is true <br/>
     * at least one values
     * 
     * @param onDup false if don's regard <code>ON DUPLICATE KEY UPDATE</code>
     *            as a part of statement
     * @return if <code>ON DUPLICATE KEY UPDATE</code>
     * @throws SQLSyntaxErrorException
     */
    protected boolean valuesList(List<List<Expression>> valuesList, boolean onDup) throws SQLSyntaxErrorException {
        while (true) {
            match(PUNC_LEFT_PAREN);
            List<Expression> values = values();
            valuesList.add(values);
            SQLToken token = input.next();
            if (token == null) {
                return false;
            }
            switch (token.getType()) {
            case PUNC_COMMA:
                continue;
            case KW_ON:
                if (onDup
                    && input.isNext(IDENTIFIER, 0, true, "DUPLICATE")
                    && input.isNext(KW_KEY, 1)
                    && input.isNext(KW_UPDATE, 2)) {
                    input.skipForce(3);
                    return true;
                }
            default:
                input.backForce();
                return false;
            }
        }
    }

    /**
     * first <code>(</code> has already been consumed.<br/>
     * consume last <code>)</code> before invocation finished
     */
    protected List<Expression> values() throws SQLSyntaxErrorException {
        List<Expression> values = new LinkedList<Expression>();
        while (true) {
            if (input.isNext(KW_DEFAULT, 0)) {
                input.skipForce(1);
                values.add(new DefaultValue());
            } else {
                Expression v = exprParser.expression();
                values.add(v);
            }
            SQLToken token = input.next();
            switch (token.getType()) {
            case PUNC_COMMA:
                continue;
            case PUNC_RIGHT_PAREN:
                return values;
            default:
                err("expect ',' or ')' in values list but is " + token);
            }
        }
    }

    /**
     * first <code>SET</code> has been consumed<br/>
     * consume followed <code>ON DUPLICATE KEY UPDATE</code> if there is one and
     * onDup is true <br/>
     * at least one assignment
     * 
     * @param onDup false if don's regard <code>ON DUPLICATE KEY UPDATE</code>
     *            as a part of statement
     * @return if <code>ON DUPLICATE KEY UPDATE</code>
     * @throws SQLSyntaxErrorException
     */
    protected boolean setColumnsFormat(List<String> columnNameList, List<List<Expression>> valuesList, boolean onDup)
            throws SQLSyntaxErrorException {
        List<Expression> values = new LinkedList<Expression>();
        while (true) {
            SQLToken token = match(IDENTIFIER);
            columnNameList.add(token.getText());
            token = input.next();
            if (token == null) err("unexpected EOF");
            switch (token.getType()) {
            case OP_ASSIGN:
            case OP_EQUALS:
                break;
            default:
                err("unexpected token: " + token);
            }
            if (input.isNext(KW_DEFAULT, 0)) {
                input.skipForce(1);
                values.add(new DefaultValue());
            } else {
                Expression expr = exprParser.expression();
                values.add(expr);
            }
            if ((token = input.next()) == null) {
                valuesList.add(values);
                return false;
            }
            switch (token.getType()) {
            case PUNC_COMMA:
                continue;
            case KW_ON:
                if (onDup
                    && input.isNext(IDENTIFIER, 0, true, "DUPLICATE")
                    && input.isNext(KW_KEY, 1)
                    && input.isNext(KW_UPDATE, 2)) {
                    input.skipForce(3);
                    valuesList.add(values);
                    return true;
                }
            default:
                input.backForce();
                valuesList.add(values);
                return false;
            }
        }
    }

    /**
     * complete SELECT statement, no token has been preconsumed before
     * invocation
     * 
     * @throws SQLSyntaxErrorException
     */
    protected Expression select() throws SQLSyntaxErrorException {
        return new DMLSelectStmtParser(input).selectUnionStmt();
    }
}
